home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 April / PCWorld_2008-04_cd.bin / v cisle / ozo / zotero-1.0.3.xpi / chrome / zotero.jar / content / zotero / itemPane.js < prev    next >
Encoding:
JavaScript  |  2007-10-21  |  46.5 KB  |  1,746 lines

  1. /*
  2.     ***** BEGIN LICENSE BLOCK *****
  3.     
  4.     Copyright (c) 2006  Center for History and New Media
  5.                         George Mason University, Fairfax, Virginia, USA
  6.                         http://chnm.gmu.edu
  7.     
  8.     Licensed under the Educational Community License, Version 1.0 (the "License");
  9.     you may not use this file except in compliance with the License.
  10.     You may obtain a copy of the License at
  11.     
  12.     http://www.opensource.org/licenses/ecl1.php
  13.     
  14.     Unless required by applicable law or agreed to in writing, software
  15.     distributed under the License is distributed on an "AS IS" BASIS,
  16.     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17.     See the License for the specific language governing permissions and
  18.     limitations under the License.
  19.     
  20.     ***** END LICENSE BLOCK *****
  21. */
  22.  
  23. var ZoteroItemPane = new function()
  24. {
  25.     var _dynamicFields;
  26.     var _creatorTypeMenu;
  27.     var _beforeRow;
  28.     var _notesList;
  29.     var _linksBox;
  30.     var _notesLabel;
  31.     
  32.     var _creatorCount;
  33.     
  34.     var _lastPane;
  35.     var _loaded;
  36.     
  37.     var _itemBeingEdited;
  38.     var _activeScrollbox;
  39.     
  40.     var _addCreatorRow;
  41.     
  42.     var _lastTabIndex;
  43.     var _tabDirection;
  44.     var _tabIndexMinCreators = 10;
  45.     var _tabIndexMaxCreators = 0;
  46.     var _tabIndexMinFields = 1000;
  47.     var _tabIndexMaxInfoFields = 0;
  48.     var _tabIndexMaxTagsFields = 0;
  49.     
  50.     const _defaultFirstName =
  51.         '(' + Zotero.getString('pane.item.defaultFirstName') + ')';
  52.     const _defaultLastName =
  53.         '(' + Zotero.getString('pane.item.defaultLastName') + ')';
  54.     const _defaultFullName =
  55.         '(' + Zotero.getString('pane.item.defaultFullName') + ')';
  56.     
  57.     this.onLoad = onLoad;
  58.     this.viewItem = viewItem;
  59.     this.scrollToTop = scrollToTop;
  60.     this.ensureElementIsVisible = ensureElementIsVisible;
  61.     this.loadPane = loadPane;
  62.     this.changeTypeTo = changeTypeTo;
  63.     this.onViewClick = onViewClick;
  64.     this.onOpenURLClick = onOpenURLClick;
  65.     this.addCreatorRow = addCreatorRow;
  66.     this.switchCreatorMode = switchCreatorMode;
  67.     this.toggleAbstractExpand = toggleAbstractExpand;
  68.     this.disableButton = disableButton;
  69.     this.createValueElement = createValueElement;
  70.     this.removeCreator = removeCreator;
  71.     this.showEditor = showEditor;
  72.     this.handleKeyPress = handleKeyPress;
  73.     this.handleCreatorAutoCompleteSelect = handleCreatorAutoCompleteSelect;
  74.     this.hideEditor = hideEditor;
  75.     this.textTransform = textTransform;
  76.     this.getCreatorFields = getCreatorFields;
  77.     this.modifyCreator = modifyCreator;
  78.     this.removeNote = removeNote;
  79.     this.addNote = addNote;
  80.     this.removeAttachment = removeAttachment;
  81.     this.addAttachmentFromDialog = addAttachmentFromDialog;
  82.     this.addAttachmentFromPage = addAttachmentFromPage;
  83.     this.focusFirstField = focusFirstField;
  84.     
  85.     
  86.     function onLoad()
  87.     {
  88.         if (!Zotero || !Zotero.initialized) {
  89.             return;
  90.         }
  91.         
  92.         _tabs = document.getElementById('zotero-view-tabs');
  93.         
  94.         // Not in item pane, so skip the introductions
  95.         if (!_tabs)
  96.         {
  97.             return;
  98.         }
  99.         
  100.         _dynamicFields = document.getElementById('zotero-editpane-dynamic-fields');
  101.         _itemTypeMenu = document.getElementById('zotero-editpane-type-menu');
  102.         _creatorTypeMenu = document.getElementById('zotero-creator-type-menu');
  103.         _notesList = document.getElementById('zotero-editpane-dynamic-notes');
  104.         _notesLabel = document.getElementById('zotero-editpane-notes-label');
  105.         _attachmentsList = document.getElementById('zotero-editpane-dynamic-attachments');
  106.         _attachmentsLabel = document.getElementById('zotero-editpane-attachments-label');
  107.         _tagsBox = document.getElementById('zotero-editpane-tags');
  108.         _relatedBox = document.getElementById('zotero-editpane-related');
  109.         
  110.         var itemTypes = Zotero.ItemTypes.getTypes();
  111.         for(var i = 0; i<itemTypes.length; i++)
  112.             if(itemTypes[i]['name'] != 'note' && itemTypes[i]['name'] != 'attachment')
  113.                 _itemTypeMenu.appendItem(Zotero.getString("itemTypes."+itemTypes[i]['name']),itemTypes[i]['id']);
  114.     }
  115.     
  116.     /*
  117.      * Loads an item 
  118.      */
  119.     function viewItem(thisItem)
  120.     {
  121.         //Zotero.debug('Viewing item');
  122.         
  123.         // Force blur() when clicking off a textbox to another item in middle
  124.         // pane, since for some reason it's not being called automatically
  125.         if (_itemBeingEdited && _itemBeingEdited!=thisItem)
  126.         {
  127.             switch (_tabs.selectedIndex)
  128.             {
  129.                 // Info
  130.                 case 0:
  131.                     var boxes = _dynamicFields.getElementsByTagName('textbox');
  132.                     
  133.                     // When coming from another element, scroll pane to top
  134.                     scrollToTop();
  135.                     break;
  136.                     
  137.                 // Tags
  138.                 case 3:
  139.                     var boxes = document.getAnonymousNodes(_tagsBox)[0].getElementsByTagName('textbox');
  140.                     break;
  141.             }
  142.             
  143.             if (boxes && boxes.length==1)
  144.             {
  145.                 boxes[0].inputField.blur();
  146.             }
  147.         }
  148.         
  149.         _itemBeingEdited = thisItem;
  150.         _loaded = {};
  151.         
  152.         loadPane(_tabs.selectedIndex);
  153.     }
  154.     
  155.     
  156.     function loadPane(index)
  157.     {
  158.         //Zotero.debug('Loading item pane ' + index);
  159.         
  160.         // Clear the tab index when switching panes
  161.         if (_lastPane!=index)
  162.         {
  163.             _lastTabIndex = null;
  164.         }
  165.         _lastPane = index;
  166.         
  167.         if(_loaded[index])
  168.         {
  169.             return;
  170.         }
  171.         _loaded[index] = true;
  172.         
  173.         // Info pane
  174.         if(index == 0)
  175.         {
  176.             _activeScrollbox = document.getElementById('zotero-info');
  177.             
  178.             // Enable/disable "View =>" button
  179.             testView: try
  180.             {
  181.                 var viewButton = document.getElementById('zotero-go-to-url');
  182.                 
  183.                 viewButton.removeAttribute('viewSnapshot');
  184.                 viewButton.removeAttribute('viewURL');
  185.                 viewButton.setAttribute('label',
  186.                     Zotero.getString('pane.item.goToURL.online.label'));
  187.                 viewButton.setAttribute('tooltiptext',
  188.                     Zotero.getString('pane.item.goToURL.online.tooltip'));
  189.                 
  190.                 var spec = false, validURI = false;
  191.                 
  192.                 var uri = Components.classes["@mozilla.org/network/standard-url;1"].
  193.                         createInstance(Components.interfaces.nsIURI);
  194.                 
  195.                 // First try to find a snapshot matching the item's URL field
  196.                 var snapID = _itemBeingEdited.getBestSnapshot();
  197.                 if (snapID) {
  198.                     spec = Zotero.Items.get(snapID).getLocalFileURL();
  199.                     uri.spec = spec;
  200.                     if (!uri.scheme || uri.scheme != 'file') {
  201.                         snapID = false;
  202.                         spec = false;
  203.                     }
  204.                 }
  205.                 
  206.                 // If that fails, try the URL field itself
  207.                 if (!spec) {
  208.                     spec = _itemBeingEdited.getField('url');
  209.                     uri.spec = spec;
  210.                     if (!(uri.scheme && (uri.host || uri.scheme == 'file'))) {
  211.                         spec = false;
  212.                     }
  213.                 }
  214.                 
  215.                 if (!spec) {
  216.                     break testView;
  217.                 }
  218.                 
  219.                 validURI = true;
  220.                 
  221.                 if (snapID) {
  222.                     viewButton.setAttribute('label',
  223.                         Zotero.getString('pane.item.goToURL.snapshot.label'));
  224.                     viewButton.setAttribute('tooltiptext',
  225.                         Zotero.getString('pane.item.goToURL.snapshot.tooltip'));
  226.                     viewButton.setAttribute('viewSnapshot', snapID);
  227.                 }
  228.                 else {
  229.                     viewButton.setAttribute('viewURL', spec);
  230.                 }
  231.             }
  232.             catch (e){Zotero.debug(e);}
  233.             viewButton.setAttribute('disabled', !validURI);
  234.             
  235.             // Enable/disable "Locate =>" (OpenURL) button
  236.             switch (_itemBeingEdited.getType())
  237.             {
  238.                 // DEBUG: handle descendents of these types as well?
  239.                 case Zotero.ItemTypes.getID('book'):
  240.                 case Zotero.ItemTypes.getID('bookSection'):
  241.                 case Zotero.ItemTypes.getID('journalArticle'):
  242.                 case Zotero.ItemTypes.getID('thesis'):
  243.                     var openURL = true;
  244.                     break;
  245.                 
  246.                 default:
  247.                     var openURL = false;
  248.             }
  249.             document.getElementById('zotero-openurl').setAttribute('disabled', !openURL);
  250.             
  251.             // Clear and rebuild creator type menu
  252.             while(_creatorTypeMenu.hasChildNodes())
  253.             {
  254.                 _creatorTypeMenu.removeChild(_creatorTypeMenu.firstChild);
  255.             }
  256.             
  257.             var creatorTypes = Zotero.CreatorTypes.getTypesForItemType(_itemBeingEdited.getType());
  258.             var localized = {};
  259.             for (var i=0; i<creatorTypes.length; i++)
  260.             {
  261.                 localized[creatorTypes[i]['name']]
  262.                     = Zotero.getString('creatorTypes.' + creatorTypes[i]['name']);
  263.             }
  264.             
  265.             for (var i in localized)
  266.             {
  267.                 var menuitem = document.createElement("menuitem");
  268.                 menuitem.setAttribute("label", localized[i]);
  269.                 menuitem.setAttribute("typeid", Zotero.CreatorTypes.getID(i));
  270.                 _creatorTypeMenu.appendChild(menuitem);
  271.             }
  272.             
  273.             
  274.             //
  275.             // Clear and rebuild metadata fields
  276.             //
  277.             while(_dynamicFields.hasChildNodes())
  278.                 _dynamicFields.removeChild(_dynamicFields.firstChild);
  279.         
  280.             for(var i = 0, len = _itemTypeMenu.firstChild.childNodes.length; i < len; i++)
  281.                 if(_itemTypeMenu.firstChild.childNodes[i].value == _itemBeingEdited.getType())
  282.                     _itemTypeMenu.selectedIndex = i;
  283.         
  284.             var fieldNames = [];
  285.             var fields = Zotero.ItemFields.getItemTypeFields(_itemBeingEdited.getField("itemTypeID"));
  286.             for (var i = 0; i<fields.length; i++) {
  287.                 fieldNames.push(Zotero.ItemFields.getName(fields[i]));
  288.             }
  289.             fieldNames.push("dateAdded","dateModified");
  290.             
  291.             for(var i = 0; i<fieldNames.length; i++)
  292.             {
  293.                 var editable = !_itemBeingEdited.isPrimaryField(fieldNames[i]);
  294.                 var fieldID = Zotero.ItemFields.getID(fieldNames[i])
  295.                 var val = _itemBeingEdited.getField(fieldNames[i]);
  296.                 
  297.                 // Start tabindex at 1000 after creators
  298.                 var tabindex = editable ? (i>0 ? _tabIndexMinFields + i : 1) : 0;
  299.                 _tabIndexMaxInfoFields = Math.max(_tabIndexMaxInfoFields, tabindex);
  300.                 
  301.                 if (editable && Zotero.ItemFields.isFieldOfBase(fieldID, 'date')) {
  302.                     addDateRow(fieldNames[i], _itemBeingEdited.getField(fieldNames[i], true), tabindex);
  303.                     continue;
  304.                 }
  305.                 
  306.                 var valueElement = createValueElement(
  307.                     val, fieldNames[i], tabindex, !editable
  308.                 );
  309.                 
  310.                 var label = document.createElement("label");
  311.                 label.setAttribute('fieldname', fieldNames[i]);
  312.                 
  313.                 var prefix = '';
  314.                 // Add '(...)' before 'Abstract:' for collapsed abstracts
  315.                 if (fieldNames[i] == 'abstractNote') {
  316.                     if (val && !Zotero.Prefs.get('lastAbstractExpand')) {
  317.                         prefix = '(...) ';
  318.                     }
  319.                 }
  320.                 label.setAttribute("value", prefix +
  321.                     Zotero.ItemFields.getLocalizedString(_itemBeingEdited.getType(), fieldNames[i]) + ":");
  322.                 
  323.                 if (fieldNames[i] == 'url' && val) {
  324.                     label.setAttribute("isButton", true);
  325.                     // TODO: make getFieldValue non-private and use below instead
  326.                     label.setAttribute("onclick", "ZoteroPane.loadURI(this.nextSibling.firstChild ? this.nextSibling.firstChild.nodeValue : this.nextSibling.value, event)");
  327.                     label.setAttribute("tooltiptext", Zotero.getString('pane.item.goToURL.online.tooltip'));
  328.                 }
  329.                 else if (fieldNames[i] == 'abstractNote') {
  330.                     label.setAttribute("onclick", "if (this.nextSibling.inputField) { this.nextSibling.inputField.blur(); } else { ZoteroItemPane.toggleAbstractExpand(this); }");
  331.                 }
  332.                 else {
  333.                     label.setAttribute("onclick", "if (this.nextSibling.inputField) { this.nextSibling.inputField.blur(); }");
  334.                 }
  335.             
  336.                 addDynamicRow(label,valueElement);
  337.             }
  338.         
  339.             //CREATORS:
  340.             _beforeRow = _dynamicFields.firstChild.nextSibling;
  341.             _creatorCount = 0;
  342.             if(_itemBeingEdited.numCreators() > 0)
  343.             {
  344.                 for(var i = 0, len=_itemBeingEdited.numCreators(); i<len; i++)
  345.                 {
  346.                     var creator = _itemBeingEdited.getCreator(i);
  347.                     addCreatorRow(creator['firstName'], creator['lastName'], creator['creatorTypeID'], creator['fieldMode']);
  348.                 }
  349.                 
  350.                 if (_addCreatorRow) {
  351.                     addCreatorRow('', '', false, Zotero.Prefs.get('lastCreatorFieldMode'), true, false);
  352.                     _addCreatorRow = false;
  353.                 }
  354.             }
  355.             else
  356.             {
  357.                 // Add default row
  358.                 addCreatorRow('', '', false, Zotero.Prefs.get('lastCreatorFieldMode'), true, true);
  359.             }
  360.             
  361.             var focusMode = 'info';
  362.             var focusBox = _dynamicFields;
  363.         }
  364.         
  365.         // Notes pane
  366.         else if(index == 1)
  367.         {
  368.             while(_notesList.hasChildNodes())
  369.                 _notesList.removeChild(_notesList.firstChild);
  370.                 
  371.             var notes = Zotero.Items.get(_itemBeingEdited.getNotes());
  372.             if(notes.length)
  373.             {
  374.                 for(var i = 0; i < notes.length; i++)
  375.                 {
  376.                     var icon = document.createElement('image');
  377.                     icon.setAttribute('src','chrome://zotero/skin/treeitem-note.png');
  378.                 
  379.                     var label = document.createElement('label');
  380.                     label.setAttribute('value',_noteToTitle(notes[i].getNote()));
  381.                     label.setAttribute('flex','1');    //so that the long names will flex smaller
  382.                     label.setAttribute('crop','end');
  383.                 
  384.                     var box = document.createElement('box');
  385.                     box.setAttribute('onclick',"ZoteroPane.selectItem("+notes[i].getID()+");");
  386.                     box.setAttribute('class','zotero-clicky');
  387.                     box.appendChild(icon);
  388.                     box.appendChild(label);
  389.                 
  390.                     var removeButton = document.createElement('label');
  391.                     removeButton.setAttribute("value","-");
  392.                     removeButton.setAttribute("class","zotero-clicky");
  393.                     removeButton.setAttribute("onclick","ZoteroItemPane.removeNote("+notes[i].getID()+")");
  394.                 
  395.                     var row = document.createElement('row');
  396.                     row.appendChild(box);
  397.                     row.appendChild(removeButton);
  398.                 
  399.                     _notesList.appendChild(row);
  400.                 }
  401.             }
  402.         
  403.             _updateNoteCount();
  404.         }
  405.         
  406.         // Attachments pane
  407.         else if(index == 2)
  408.         {
  409.             while(_attachmentsList.hasChildNodes())
  410.                 _attachmentsList.removeChild(_attachmentsList.firstChild);
  411.                 
  412.             var attachments = Zotero.Items.get(_itemBeingEdited.getAttachments());
  413.             if(attachments.length)
  414.             {
  415.                 for(var i = 0; i < attachments.length; i++)
  416.                 {
  417.                     var icon = document.createElement('image');
  418.                     var linkMode = attachments[i].getAttachmentLinkMode();
  419.                     var itemType = '';
  420.                     if(linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE)
  421.                     {
  422.                         itemType = "-file";
  423.                     }
  424.                     else if(linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE)
  425.                     {
  426.                         itemType = "-link";
  427.                     }
  428.                     else if(linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL)
  429.                     {
  430.                         itemType = "-snapshot";
  431.                     }
  432.                     else if(linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL)
  433.                     {
  434.                         itemType = "-web-link";
  435.                     }
  436.                     icon.setAttribute('src','chrome://zotero/skin/treeitem-file'+itemType+'.png');
  437.                 
  438.                     var label = document.createElement('label');
  439.                     label.setAttribute('value',attachments[i].getField('title'));
  440.                     label.setAttribute('flex','1');    //so that the long names will flex smaller
  441.                     label.setAttribute('crop','end');
  442.                 
  443.                     var box = document.createElement('box');
  444.                     box.setAttribute('onclick',"ZoteroPane.selectItem('"+attachments[i].getID()+"')");
  445.                     box.setAttribute('class','zotero-clicky');
  446.                     box.appendChild(icon);
  447.                     box.appendChild(label);
  448.                 
  449.                     var removeButton = document.createElement('label');
  450.                     removeButton.setAttribute("value","-");
  451.                     removeButton.setAttribute("class","zotero-clicky");
  452.                     removeButton.setAttribute("onclick","ZoteroItemPane.removeAttachment("+attachments[i].getID()+")");
  453.                 
  454.                     var row = document.createElement('row');
  455.                     row.appendChild(box);
  456.                     row.appendChild(removeButton);
  457.                 
  458.                     _attachmentsList.appendChild(row);
  459.                 }
  460.             }
  461.         
  462.             _updateAttachmentCount();
  463.             
  464.         }
  465.         
  466.         // Tags pane
  467.         else if(index == 3)
  468.         {
  469.             _activeScrollbox = document.getElementById('zotero-editpane-tags').getScrollBox();
  470.             var focusMode = 'tags';
  471.             var focusBox = _tagsBox;
  472.             _tagsBox.item = _itemBeingEdited;
  473.         }
  474.         
  475.         // Related pane
  476.         else if(index == 4)
  477.         {
  478.             _relatedBox.item = _itemBeingEdited;
  479.         }
  480.         
  481.         
  482.         // Move to next or previous field if (shift-)tab was pressed
  483.         if (focusMode && _lastTabIndex && _tabDirection)
  484.         {
  485.             _focusNextField(focusMode, focusBox, _lastTabIndex, _tabDirection==-1);
  486.         }
  487.     }
  488.     
  489.     
  490.     function scrollToTop() {
  491.         if (!_activeScrollbox) {
  492.             return;
  493.         }
  494.         var sbo = _activeScrollbox.boxObject;
  495.         sbo.QueryInterface(Components.interfaces.nsIScrollBoxObject);
  496.         sbo.scrollTo(0,0);
  497.     }
  498.     
  499.     
  500.     function ensureElementIsVisible(elem) {
  501.         if (!_activeScrollbox) {
  502.             return;
  503.         }
  504.         var sbo = _activeScrollbox.boxObject;
  505.         sbo.QueryInterface(Components.interfaces.nsIScrollBoxObject);
  506.         sbo.ensureElementIsVisible(elem);
  507.     }
  508.     
  509.     
  510.     function changeTypeTo(itemTypeID, menu) {
  511.         if (itemTypeID == _itemBeingEdited.getType()) {
  512.             return;
  513.         }
  514.         
  515.         var fieldsToDelete = _itemBeingEdited.getFieldsNotInType(itemTypeID, true);
  516.         
  517.         // Generate list of localized field names for display in pop-up
  518.         if (fieldsToDelete) {
  519.             var fieldNames = "";
  520.             for (var i=0; i<fieldsToDelete.length; i++) {
  521.                 fieldNames += "\n - " +
  522.                     Zotero.ItemFields.getLocalizedString(_itemBeingEdited.getType(), fieldsToDelete[i]);
  523.             }
  524.             
  525.             var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  526.                 .getService(Components.interfaces.nsIPromptService);
  527.         }
  528.         
  529.         if (!fieldsToDelete ||
  530.                 promptService.confirm(null,
  531.                     Zotero.getString('pane.item.changeType.title'),
  532.                     Zotero.getString('pane.item.changeType.text') + "\n" + fieldNames)) {
  533.             _itemBeingEdited.setType(itemTypeID);
  534.             _itemBeingEdited.save();
  535.             loadPane(0);
  536.         }
  537.         // Revert the menu (which changes before the pop-up)
  538.         else {
  539.             menu.value = _itemBeingEdited.getType();
  540.         }
  541.     }
  542.     
  543.     function onViewClick(button, event) {
  544.         if (button.getAttribute('viewURL')) {
  545.             ZoteroPane.loadURI(button.getAttribute('viewURL'), event);
  546.         }
  547.         else if (button.getAttribute('viewSnapshot')) {
  548.             ZoteroPane.viewAttachment(button.getAttribute('viewSnapshot'), event);
  549.         }
  550.     }
  551.     
  552.     function onOpenURLClick(event)
  553.     {
  554.         var url = Zotero.OpenURL.resolve(_itemBeingEdited);
  555.         if (url)
  556.         {
  557.             ZoteroPane.loadURI(url, event);
  558.         }
  559.     }
  560.     
  561.     function addDynamicRow(label, value, beforeElement)
  562.     {
  563.         var row = document.createElement("row");
  564.         row.appendChild(label);
  565.         row.appendChild(value);
  566.         if(beforeElement)
  567.             _dynamicFields.insertBefore(row, _beforeRow);
  568.         else    
  569.             _dynamicFields.appendChild(row);        
  570.     }
  571.     
  572.     function addCreatorRow(firstName, lastName, typeID, singleField, unsaved, defaultRow)
  573.     {
  574.         // Disable the "+" button on previous rows
  575.         var elems = _dynamicFields.getElementsByAttribute('value', '+');
  576.         if (elems.length){
  577.             ZoteroItemPane.disableButton(elems[elems.length-1]);
  578.         }
  579.         
  580.         if (singleField)
  581.         {
  582.             if (!lastName)
  583.             {
  584.                 lastName = _defaultFullName;
  585.             }
  586.         }
  587.         else
  588.         {
  589.             if (!firstName)
  590.             {
  591.                 firstName = _defaultFirstName;
  592.             }
  593.             if (!lastName)
  594.             {
  595.                 lastName = _defaultLastName;
  596.             }
  597.         }
  598.         
  599.         // Use the first entry in the drop-down for the default type
  600.         if (!typeID)
  601.         {
  602.             typeID = _creatorTypeMenu.childNodes[0].getAttribute('typeid');
  603.         }
  604.         
  605.         var label = document.createElement("toolbarbutton");
  606.         label.setAttribute("label",Zotero.getString('creatorTypes.'+Zotero.CreatorTypes.getName(typeID))+":");
  607.         label.setAttribute("typeid", typeID);
  608.         label.setAttribute("popup","zotero-creator-type-menu");
  609.         label.setAttribute("fieldname",'creator-'+_creatorCount+'-typeID');
  610.         label.className = 'zotero-clicky';
  611.         
  612.         // getCreatorFields(), switchCreatorMode() and handleCreatorAutoCompleteSelect()
  613.         // may need need to be adjusted if this DOM structure changes
  614.         var hbox = document.createElement("hbox");
  615.         
  616.         // Name
  617.         var firstlast = document.createElement("hbox");
  618.         firstlast.setAttribute("flex","1");
  619.         var tabindex = _tabIndexMinCreators + (_creatorCount * 2);
  620.         var lastNameLabel = firstlast.appendChild(
  621.             createValueElement(
  622.                 lastName,
  623.                 'creator-' + _creatorCount + '-lastName',
  624.                 tabindex
  625.             )
  626.         );
  627.         
  628.         // Comma
  629.         var comma = document.createElement('label');
  630.         comma.setAttribute('value', ',');
  631.         comma.className = 'comma';
  632.         firstlast.appendChild(comma);
  633.         
  634.         firstlast.appendChild(
  635.             createValueElement(
  636.                 firstName,
  637.                 'creator-' + _creatorCount + '-firstName',
  638.                 tabindex + 1
  639.             )
  640.         );
  641.         if (singleField)
  642.         {
  643.             firstlast.lastChild.setAttribute('hidden', true);
  644.         }
  645.         _tabIndexMaxCreators = Math.max(_tabIndexMaxCreators, tabindex);
  646.         
  647.         hbox.appendChild(firstlast);
  648.         
  649.         // Single/double field toggle
  650.         var toggleButton = document.createElement('toolbarbutton');
  651.         toggleButton.setAttribute('fieldname', 'creator-' + _creatorCount + '-singleField');
  652.         toggleButton.className = 'zotero-clicky';
  653.         hbox.appendChild(toggleButton);
  654.         
  655.         // Minus (-) button
  656.         var removeButton = document.createElement('label');
  657.         removeButton.setAttribute("value","-");
  658.         // If default first row, don't let user remove it
  659.         if (defaultRow){
  660.             disableButton(removeButton);
  661.         }
  662.         else {
  663.             removeButton.setAttribute("class","zotero-clicky");
  664.             removeButton.setAttribute("onclick","ZoteroItemPane.removeCreator("+_creatorCount+", this.parentNode.parentNode)");
  665.         }
  666.         hbox.appendChild(removeButton);
  667.         
  668.         // Plus (+) button
  669.         var addButton = document.createElement('label');
  670.         addButton.setAttribute("value","+");
  671.         addButton.setAttribute("class","zotero-clicky");
  672.         // If row isn't saved, don't let user add more
  673.         if (unsaved)
  674.         {
  675.             disableButton(addButton);
  676.         }
  677.         else
  678.         {
  679.             _enablePlusButton(addButton, typeID, singleField);
  680.         }
  681.         hbox.appendChild(addButton);
  682.         
  683.         _creatorCount++;
  684.         
  685.         addDynamicRow(label, hbox, true);
  686.         
  687.         // Set single/double field toggle mode
  688.         if (singleField)
  689.         {
  690.             switchCreatorMode(hbox.parentNode, true, true);
  691.         }
  692.         else
  693.         {
  694.             switchCreatorMode(hbox.parentNode, false, true);
  695.         }
  696.         
  697.         // Focus new rows
  698.         if (unsaved && !defaultRow){
  699.             lastNameLabel.click();
  700.         }
  701.     }
  702.     
  703.     
  704.     /**
  705.      * Add a date row with a label editor and a ymd indicator to show date parsing
  706.      */
  707.     function addDateRow(field, value, tabindex)
  708.     {
  709.         var label = document.createElement("label");
  710.         label.setAttribute("value", Zotero.getString("itemFields." + field) + ':');
  711.         label.setAttribute("fieldname", field);
  712.         label.setAttribute("onclick", "this.nextSibling.firstChild.blur()");
  713.         
  714.         var hbox = document.createElement("hbox");
  715.         var elem = createValueElement(Zotero.Date.multipartToStr(value), field, tabindex);
  716.         
  717.         // y-m-d status indicator
  718.         var datebox = document.createElement('hbox');
  719.         datebox.className = 'zotero-date-field-status';
  720.         var year = document.createElement('label');
  721.         var month = document.createElement('label');
  722.         var day = document.createElement('label');
  723.         year.setAttribute('value', Zotero.getString('date.abbreviation.year'));
  724.         month.setAttribute('value', Zotero.getString('date.abbreviation.month'));
  725.         day.setAttribute('value', Zotero.getString('date.abbreviation.day'));
  726.         
  727.         // Display the date parts we have and hide the others
  728.         var sqldate = Zotero.Date.multipartToSQL(value);
  729.         year.setAttribute('hidden', !Zotero.Date.sqlHasYear(sqldate));
  730.         month.setAttribute('hidden', !Zotero.Date.sqlHasMonth(sqldate));
  731.         day.setAttribute('hidden', !Zotero.Date.sqlHasDay(sqldate));
  732.         
  733.         datebox.appendChild(year);
  734.         datebox.appendChild(month);
  735.         datebox.appendChild(day);
  736.         
  737.         var hbox = document.createElement('hbox');
  738.         hbox.setAttribute('flex', 1);
  739.         hbox.appendChild(elem);
  740.         hbox.appendChild(datebox);
  741.         
  742.         addDynamicRow(label, hbox);
  743.     }
  744.     
  745.     
  746.     function switchCreatorMode(row, singleField, initial)
  747.     {
  748.         // Change if button position changes
  749.         // row->hbox->label->label->toolbarbutton
  750.         var button = row.lastChild.lastChild.previousSibling.previousSibling;
  751.         var hbox = button.previousSibling;
  752.         var lastName = hbox.firstChild;
  753.         var comma = hbox.firstChild.nextSibling;
  754.         var firstName = hbox.lastChild;
  755.         
  756.         // Switch to single-field mode
  757.         if (singleField)
  758.         {
  759.             button.setAttribute('image', 'chrome://zotero/skin/textfield-dual.png');
  760.             button.setAttribute('tooltiptext', Zotero.getString('pane.item.switchFieldMode.two'));
  761.             lastName.setAttribute('singleField', 'true');
  762.             button.setAttribute('onclick', "ZoteroItemPane.switchCreatorMode(this.parentNode.parentNode, false)");
  763.             lastName.setAttribute('flex', '1');
  764.             
  765.             // Remove firstname field from tabindex
  766.             var tab = parseInt(firstName.getAttribute('ztabindex'));
  767.             firstName.setAttribute('ztabindex', -1);
  768.             if (_tabIndexMaxCreators==tab)
  769.             {
  770.                 _tabIndexMaxCreators--;
  771.             }
  772.             
  773.             // Hide first name field and prepend to last name field
  774.             firstName.setAttribute('hidden', true);
  775.             comma.setAttribute('hidden', true);
  776.             
  777.             if (!initial){
  778.                 var first = _getFieldValue(firstName);
  779.                 if (first && first != _defaultFirstName)
  780.                 {
  781.                     var last = _getFieldValue(lastName);
  782.                     _setFieldValue(lastName, first + ' ' + last);
  783.                 }
  784.             }
  785.             
  786.             if (_getFieldValue(lastName) == _defaultLastName)
  787.             {
  788.                 _setFieldValue(lastName, _defaultFullName);
  789.             }
  790.         }
  791.         // Switch to two-field mode
  792.         else
  793.         {
  794.             button.setAttribute('image', 'chrome://zotero/skin/textfield-single.png');
  795.             button.setAttribute('tooltiptext', Zotero.getString('pane.item.switchFieldMode.one'));
  796.             lastName.setAttribute('singleField', 'false');
  797.             button.setAttribute('onclick', "ZoteroItemPane.switchCreatorMode(this.parentNode.parentNode, true)");
  798.             lastName.setAttribute('flex', '0');
  799.             
  800.             // Add firstname field to tabindex
  801.             var tab = parseInt(lastName.getAttribute('ztabindex'));
  802.             firstName.setAttribute('ztabindex', tab + 1);
  803.             if (_tabIndexMaxCreators==tab)
  804.             {
  805.                 _tabIndexMaxCreators++;
  806.             }
  807.             
  808.             if (!initial){
  809.                 // Move all but last word to first name field and show it
  810.                 var last = _getFieldValue(lastName);
  811.                 if (last && last != _defaultFullName)
  812.                 {
  813.                     var lastNameRE = /(.*?)[ ]*([^ ]+[ ]*)$/;
  814.                     var parts = lastNameRE.exec(last);
  815.                     if (parts[2] && parts[2] != last)
  816.                     {
  817.                         _setFieldValue(lastName, parts[2]);
  818.                         _setFieldValue(firstName, parts[1]);
  819.                     }
  820.                 }
  821.             }
  822.             
  823.             if (!_getFieldValue(firstName))
  824.             {
  825.                 _setFieldValue(firstName, _defaultFirstName);
  826.             }
  827.             
  828.             if (_getFieldValue(lastName) == _defaultFullName)
  829.             {
  830.                 _setFieldValue(lastName, _defaultLastName);
  831.             }
  832.             
  833.             firstName.setAttribute('hidden', false);
  834.             comma.setAttribute('hidden', false);
  835.         }
  836.         
  837.         // Save the last-used field mode
  838.         Zotero.Prefs.set('lastCreatorFieldMode', singleField);
  839.         
  840.         if (!initial)
  841.         {
  842.             var [, index, field] = button.getAttribute('fieldname').split('-');
  843.             
  844.             var otherFields = getCreatorFields(row); // row
  845.             modifyCreator(index, field, !!singleField, otherFields);
  846.         }
  847.     }
  848.     
  849.     
  850.     function toggleAbstractExpand(label) {
  851.         var cur = Zotero.Prefs.get('lastAbstractExpand');
  852.         Zotero.Prefs.set('lastAbstractExpand', !cur);
  853.         
  854.         var ab = label.nextSibling;
  855.         var valueText = _itemBeingEdited.getField('abstractNote');
  856.         var tabindex = ab.getAttribute('ztabindex');
  857.         var elem = createValueElement(valueText, 'abstractNote', tabindex);
  858.         ab.parentNode.replaceChild(elem, ab);
  859.         
  860.         var text = Zotero.ItemFields.getLocalizedString(_itemBeingEdited.getType(), 'abstractNote') + ':';
  861.         // Add '(...)' before "Abstract:" for collapsed abstracts
  862.         if (valueText && cur) {
  863.             text = '(...) ' + text;
  864.         }
  865.         label.setAttribute('value', text);
  866.     }
  867.     
  868.     
  869.     function disableButton(button)
  870.     {
  871.         button.setAttribute('disabled', true);
  872.         button.setAttribute('onclick', false); 
  873.     }
  874.     
  875.     function _enablePlusButton(button, creatorTypeID, fieldMode)
  876.     {
  877.         button.setAttribute('disabled', false);
  878.         button.setAttribute("onclick",
  879.             "ZoteroItemPane.disableButton(this); ZoteroItemPane.addCreatorRow('', '', " + (creatorTypeID ? creatorTypeID : 'false') + ", " + fieldMode + ", true);");
  880.     }
  881.     
  882.     function createValueElement(valueText, fieldName, tabindex, noedit)
  883.     {
  884.         var fieldID = Zotero.ItemFields.getID(fieldName);
  885.         
  886.         // If an abstract, check last expand state
  887.         var abstractAsVbox = (fieldName == 'abstractNote') &&
  888.             Zotero.Prefs.get('lastAbstractExpand');
  889.         
  890.         if (fieldName == 'extra' || abstractAsVbox) {
  891.             var valueElement = document.createElement("vbox");
  892.         }
  893.         else
  894.         {
  895.             var valueElement = document.createElement("label");
  896.         }
  897.         
  898.         valueElement.setAttribute('fieldname',fieldName);
  899.         
  900.         if (!noedit){
  901.             valueElement.setAttribute('flex', 1);
  902.             valueElement.setAttribute('ztabindex', tabindex);
  903.             valueElement.setAttribute('onclick', '/* Skip right-click on Windows */ if (event.button) { return; } ZoteroItemPane.showEditor(this)');
  904.             valueElement.className = 'zotero-clicky';
  905.         }
  906.         
  907.         switch (fieldName) {
  908.             case 'tag':
  909.                 _tabIndexMaxTagsFields = Math.max(_tabIndexMaxTagsFields, tabindex);
  910.                 break;
  911.             
  912.             // Convert dates from UTC
  913.             case 'dateAdded':
  914.             case 'dateModified':
  915.             case 'accessDate':
  916.                 if (valueText){
  917.                     var date = Zotero.Date.sqlToDate(valueText, true);
  918.                     valueText = date ? date.toLocaleString() : '';
  919.                     
  920.                     // Don't show time for access date if none
  921.                     if (fieldName == 'accessDate') {
  922.                         valueText = valueText.replace('00:00:00 ', '');
  923.                     }
  924.                 }
  925.                 break;
  926.         }
  927.         
  928.         if (fieldID) {
  929.             // Display the SQL date as a tooltip for date fields
  930.             if (Zotero.ItemFields.isFieldOfBase(fieldID, 'date')) {
  931.                 valueElement.setAttribute('tooltiptext',
  932.                     Zotero.Date.multipartToSQL(_itemBeingEdited.getField(fieldName, true)));
  933.             }
  934.             
  935.             // Display a context menu for certain fields
  936.             if (fieldName == 'seriesTitle' || fieldName == 'shortTitle' ||
  937.                     Zotero.ItemFields.isFieldOfBase(fieldID, 'title') ||
  938.                     Zotero.ItemFields.isFieldOfBase(fieldID, 'publicationTitle')) {
  939.                 valueElement.setAttribute('contextmenu', 'zotero-field-menu');
  940.             }
  941.         }
  942.         
  943.         
  944.         if (fieldName.indexOf('firstName')!=-1){
  945.             valueElement.setAttribute('flex', '1');
  946.         }
  947.         
  948.         var firstSpace;
  949.         if(typeof valueText == 'string')
  950.             firstSpace = valueText.indexOf(" ");
  951.         
  952.         // To support newlines in 'extra' fields, we use multiple
  953.         // <description> elements inside a vbox
  954.         if (fieldName == 'extra' || abstractAsVbox) {
  955.             var lines = valueText.split("\n");
  956.             for (var i = 0; i < lines.length; i++) {
  957.                 var descriptionNode = document.createElement("description");
  958.                 var linetext = document.createTextNode(lines[i]);
  959.                 descriptionNode.appendChild(linetext);
  960.                 valueElement.appendChild(descriptionNode);
  961.             }
  962.         }
  963.         // 29 == arbitrary length at which to chop uninterrupted text
  964.         else if ((firstSpace == -1 && valueText.length > 29 ) || firstSpace > 29
  965.             || (fieldName &&
  966.                 (fieldName.substr(0, 7) == 'creator') || fieldName == 'abstractNote')) {
  967.             if (fieldName == 'abstractNote') {
  968.                 valueText = valueText.replace(/[\t\n]/g, ' ');
  969.             }
  970.             valueElement.setAttribute('crop', 'end');
  971.             valueElement.setAttribute('value',valueText);
  972.         }
  973.         else
  974.         {
  975.             // Wrap to multiple lines
  976.             valueElement.appendChild(document.createTextNode(valueText));
  977.         }
  978.         
  979.         return valueElement;
  980.     }
  981.     
  982.     function removeCreator(index, labelToDelete)
  983.     {
  984.         // If unsaved row, just remove element
  985.         if (!_itemBeingEdited.hasCreatorAt(index)){
  986.             labelToDelete.parentNode.removeChild(labelToDelete);
  987.             
  988.             // Enable the "+" button on the previous row
  989.             var elems = _dynamicFields.getElementsByAttribute('value', '+');
  990.             var button = elems[elems.length-1];
  991.             var creatorFields = getCreatorFields(Zotero.getAncestorByTagName(button, 'row'));
  992.             _enablePlusButton(button, creatorFields.typeID, creatorFields.singleField);
  993.             
  994.             _creatorCount--;
  995.             return;
  996.         }
  997.         _itemBeingEdited.removeCreator(index);
  998.         _itemBeingEdited.save();
  999.         loadPane(0);
  1000.     }
  1001.     
  1002.     function showEditor(elem)
  1003.     {
  1004.         // Blur any active fields
  1005.         if (_dynamicFields) {
  1006.             _dynamicFields.focus();
  1007.         }
  1008.         
  1009.         //Zotero.debug('Showing editor');
  1010.         
  1011.         var fieldName = elem.getAttribute('fieldname');
  1012.         var tabindex = elem.getAttribute('ztabindex');
  1013.         
  1014.         var [field, creatorIndex, creatorField] = fieldName.split('-');
  1015.         if (field == 'creator')
  1016.         {
  1017.             var c = _itemBeingEdited.getCreator(creatorIndex);
  1018.             var value = c ? c[creatorField] : '';
  1019.             var itemID = _itemBeingEdited.getID();
  1020.         }
  1021.         else if (fieldName=='tag')
  1022.         {
  1023.             var tagID = elem.parentNode.getAttribute('id').split('-')[1];
  1024.             var value = tagID ? Zotero.Tags.getName(tagID) : '';
  1025.             var itemID = Zotero.getAncestorByTagName(elem, 'tagsbox').item.getID();
  1026.         }
  1027.         else
  1028.         {
  1029.             var value = _itemBeingEdited.getField(fieldName);
  1030.             var itemID = _itemBeingEdited.getID();
  1031.             
  1032.             // Access date needs to be converted from UTC
  1033.             if (fieldName=='accessDate' && value!='')
  1034.             {
  1035.                 var localDate = Zotero.Date.sqlToDate(value, true);
  1036.                 var value = Zotero.Date.dateToSQL(localDate);
  1037.             }
  1038.         }
  1039.         
  1040.         var t = document.createElement("textbox");
  1041.         t.setAttribute('value',value);
  1042.         t.setAttribute('fieldname', fieldName);
  1043.         t.setAttribute('ztabindex', tabindex);
  1044.         t.setAttribute('flex','1');
  1045.         
  1046.         if (creatorField=='lastName')
  1047.         {
  1048.             t.setAttribute('singleField', elem.getAttribute('singleField'));
  1049.         }
  1050.         
  1051.         if (['title', 'abstractNote', 'extra'].indexOf(fieldName) != -1) {
  1052.             t.setAttribute('multiline', true);
  1053.             t.setAttribute('rows', 8);
  1054.         }
  1055.         else
  1056.         {
  1057.             var autoCompleteFields = [
  1058.                 'creator',
  1059.                 'journalAbbreviation',
  1060.                 'seriesTitle',
  1061.                 'seriesText',
  1062.                 'repository',
  1063.                 'callNumber',
  1064.                 'archiveLocation',
  1065.                 'language',
  1066.                 'rights',
  1067.                 'tag'
  1068.             ];
  1069.             
  1070.             // Add the type-specific versions of these base fields
  1071.             var baseACFields = ['publisher', 'publicationTitle', 'type',
  1072.                 'medium', 'place'];
  1073.             autoCompleteFields = autoCompleteFields.concat(baseACFields);
  1074.             
  1075.             for (var i=0; i<baseACFields.length; i++) {
  1076.                 var add = Zotero.ItemFields.getTypeFieldsFromBase(baseACFields[i], true)
  1077.                 autoCompleteFields = autoCompleteFields.concat(add);
  1078.             }
  1079.             
  1080.             // Add auto-complete for certain fields
  1081.             if (autoCompleteFields.indexOf(field) != -1) {
  1082.                 t.setAttribute('type', 'autocomplete');
  1083.                 t.setAttribute('autocompletesearch', 'zotero');
  1084.                 var suffix = itemID ? itemID : '';
  1085.                 if (field=='creator') {
  1086.                     suffix = (elem.getAttribute('singleField')=='true'
  1087.                         ? '1' : '0') + '-' + suffix;
  1088.                 }
  1089.                 t.setAttribute('autocompletesearchparam', fieldName + '/' + suffix);
  1090.                 t.setAttribute('ontextentered',
  1091.                         'ZoteroItemPane.handleCreatorAutoCompleteSelect(this)');
  1092.             }
  1093.         }
  1094.         var box = elem.parentNode;
  1095.         box.replaceChild(t,elem);
  1096.         
  1097.         t.select();
  1098.         
  1099.         t.setAttribute('onblur',"ZoteroItemPane.hideEditor(this, true)");
  1100.         t.setAttribute('onkeypress',"return ZoteroItemPane.handleKeyPress(event)");
  1101.         
  1102.         _tabDirection = false;
  1103.         _lastTabIndex = tabindex;
  1104.         
  1105.         return t;
  1106.     }
  1107.     
  1108.     
  1109.     /*
  1110.      * Save a multiple-field selection for the creator autocomplete
  1111.      * (e.g. "Shakespeare, William")
  1112.      */
  1113.     function handleCreatorAutoCompleteSelect(textbox)
  1114.     {
  1115.         var comment = Zotero.Utilities.AutoComplete.getResultComment(textbox);
  1116.         if (!comment)
  1117.         {
  1118.             return;
  1119.         }
  1120.         
  1121.         var [creatorID, numFields] = comment.split('-');
  1122.         
  1123.         // If result uses two fields, save both
  1124.         if (numFields==2)
  1125.         {
  1126.             var [field, creatorIndex, creatorField] =
  1127.                 textbox.getAttribute('fieldname').split('-');
  1128.             
  1129.             var creator = Zotero.Creators.get(creatorID);
  1130.             
  1131.             var otherField = creatorField=='lastName' ? 'firstName' : 'lastName';
  1132.             
  1133.             // Update this textbox
  1134.             textbox.setAttribute('value', creator[creatorField]);
  1135.             textbox.value = creator[creatorField];
  1136.             
  1137.             // Update the other label
  1138.             if (otherField=='firstName'){
  1139.                 var label = textbox.nextSibling.nextSibling;
  1140.             }
  1141.             else if (otherField=='lastName'){
  1142.                 var label = textbox.previousSibling.previousSibling;
  1143.             }
  1144.             
  1145.             if (label.firstChild){
  1146.                 label.firstChild.nodeValue = creator[otherField];
  1147.             }
  1148.             else {
  1149.                 label.value = creator[otherField];
  1150.             }
  1151.             
  1152.             var row = textbox.parentNode.parentNode.parentNode;
  1153.             var otherFields = ZoteroItemPane.getCreatorFields(row);
  1154.             otherFields[otherField] = creator[otherField];
  1155.             
  1156.             ZoteroItemPane.modifyCreator(creatorIndex, creatorField,
  1157.                 creator[creatorField], otherFields);
  1158.         }
  1159.         
  1160.         // Otherwise let the autocomplete popup handle matters
  1161.     }
  1162.     
  1163.     function handleKeyPress(event){
  1164.         var target = event.target;
  1165.         var focused = document.commandDispatcher.focusedElement;
  1166.         
  1167.         switch (event.keyCode)
  1168.         {
  1169.             case event.DOM_VK_RETURN:
  1170.                 var fieldname = target.getAttribute('fieldname');
  1171.                 // Use shift-enter as the save action for the larger fields
  1172.                 if ((fieldname == 'abstractNote' || fieldname == 'extra')
  1173.                     && !event.shiftKey)
  1174.                 {
  1175.                     break;
  1176.                 }
  1177.                 else if (fieldname == 'tag')
  1178.                 {
  1179.                     // If last tag row, create new one
  1180.                     var row = target.parentNode.parentNode;
  1181.                     if (row == row.parentNode.lastChild)
  1182.                     {
  1183.                         _tabDirection = 1;
  1184.                         var lastTag = true;
  1185.                     }
  1186.                 }
  1187.                 // Shift-enter adds new creator row
  1188.                 else if (fieldname.indexOf('creator-') == 0 && event.shiftKey) {
  1189.                     // Value hasn't changed
  1190.                     if (target.getAttribute('value') == target.value) {
  1191.                         Zotero.debug("Value hasn't changed");
  1192.                         // If + button is disabled, just focus next creator row
  1193.                         if (Zotero.getAncestorByTagName(target, 'row').lastChild.lastChild.disabled) {
  1194.                             _focusNextField('info', _dynamicFields, _lastTabIndex, false);
  1195.                         }
  1196.                         else {
  1197.                             ZoteroItemPane.addCreatorRow('', '', false, Zotero.Prefs.get('lastCreatorFieldMode'), true, false);
  1198.                         }
  1199.                     }
  1200.                     // Value has changed
  1201.                     else {
  1202.                         _tabDirection = 1;
  1203.                         _addCreatorRow = true;
  1204.                         focused.blur();
  1205.                     }
  1206.                     return false;
  1207.                 }
  1208.                 focused.blur();
  1209.                 
  1210.                 // Return focus to items pane
  1211.                 if (!lastTag) {
  1212.                     var tree = document.getElementById('zotero-items-tree');
  1213.                     if (tree) {
  1214.                         tree.focus();
  1215.                     }
  1216.                 }
  1217.                 
  1218.                 return false;
  1219.                 
  1220.             case event.DOM_VK_ESCAPE:
  1221.                 // Reset field to original value
  1222.                 target.value = target.getAttribute('value');
  1223.                 focused.blur();
  1224.                 
  1225.                 // Return focus to items pane
  1226.                 var tree = document.getElementById('zotero-items-tree');
  1227.                 if (tree) {
  1228.                     tree.focus();
  1229.                 }
  1230.                 
  1231.                 return false;
  1232.                 
  1233.             case event.DOM_VK_TAB:
  1234.                 _tabDirection = event.shiftKey ? -1 : 1;
  1235.                 // Blur the old manually -- not sure why this is necessary,
  1236.                 // but it prevents an immediate blur() on the next tag
  1237.                 focused.blur();
  1238.                 return false;
  1239.         }
  1240.         
  1241.         return true;
  1242.     }
  1243.     
  1244.     function hideEditor(t, saveChanges)
  1245.     {
  1246.         //Zotero.debug('Hiding editor');
  1247.         var textbox = Zotero.getAncestorByTagName(t, 'textbox');
  1248.         if (!textbox){
  1249.             Zotero.debug('Textbox not found in hideEditor');
  1250.             return;
  1251.         }
  1252.         var fieldName = textbox.getAttribute('fieldname');
  1253.         var tabindex = textbox.getAttribute('ztabindex');
  1254.         
  1255.         var value = t.value;
  1256.         
  1257.         var elem;
  1258.         var [field, creatorIndex, creatorField] = fieldName.split('-');
  1259.         
  1260.         // Creator fields
  1261.         if (field == 'creator')
  1262.         {
  1263.             var row = textbox.parentNode.parentNode.parentNode;
  1264.             
  1265.             var otherFields = getCreatorFields(row);
  1266.             
  1267.             if (saveChanges){
  1268.                 modifyCreator(creatorIndex, creatorField, value, otherFields);
  1269.             }
  1270.             
  1271.             var val = _itemBeingEdited.getCreator(creatorIndex)[creatorField];
  1272.             
  1273.             if (!val){
  1274.                 // Reset to '(first)'/'(last)'/'(name)'
  1275.                 if (creatorField=='lastName')
  1276.                 {
  1277.                     val = otherFields['singleField']
  1278.                         ? _defaultFullName : _defaultLastName;
  1279.                 }
  1280.                 else if (creatorField=='firstName')
  1281.                 {
  1282.                     val = _defaultFirstName;
  1283.                 }
  1284.             }
  1285.             
  1286.             elem = createValueElement(val, fieldName, tabindex);
  1287.             
  1288.             // Reset creator mode settings
  1289.             if (otherFields['singleField'])
  1290.             {
  1291.                 switchCreatorMode(row, true, true);
  1292.             }
  1293.             else
  1294.             {
  1295.                 switchCreatorMode(row, false, true);
  1296.             }
  1297.         }
  1298.         
  1299.         // Tags
  1300.         else if (fieldName=='tag')
  1301.         {
  1302.             var tagsbox = Zotero.getAncestorByTagName(textbox, 'tagsbox');
  1303.             if (!tagsbox)
  1304.             {
  1305.                 Zotero.debug('Tagsbox not found', 1);
  1306.                 return;
  1307.             }
  1308.             
  1309.             var row = textbox.parentNode;
  1310.             var rows = row.parentNode;
  1311.             
  1312.             // Tag id encoded as 'tag-1234'
  1313.             var id = row.getAttribute('id').split('-')[1];
  1314.             
  1315.             if (saveChanges)
  1316.             {
  1317.                 if (id)
  1318.                 {
  1319.                     if (value)
  1320.                     {
  1321.                         // If trying to replace with another existing tag
  1322.                         // (which causes a delete of the row),
  1323.                         // clear the tab direction so we don't advance
  1324.                         // when the notifier kicks in
  1325.                         var existing = Zotero.Tags.getID(value, 0);
  1326.                         if (existing && id != existing)
  1327.                         {
  1328.                             _tabDirection = false;
  1329.                         }
  1330.                         var changed = tagsbox.replace(id, value);
  1331.                         if (changed)
  1332.                         {
  1333.                             return;
  1334.                         }
  1335.                     }
  1336.                     else
  1337.                     {
  1338.                         tagsbox.remove(id);
  1339.                         return;
  1340.                     }
  1341.                 }
  1342.                 // New tag
  1343.                 else
  1344.                 {
  1345.                     // If this is an existing automatic tag, it's going to be
  1346.                     // deleted and the number of rows will stay the same,
  1347.                     // so we have to compensate
  1348.                     var existingTypes = Zotero.Tags.getTypes(value);
  1349.                     if (existingTypes && existingTypes.indexOf(1) != -1) {
  1350.                         _lastTabIndex--;
  1351.                     }
  1352.                     
  1353.                     var id = tagsbox.add(value);
  1354.                     
  1355.                     // DEBUG: why does this need to continue if added?
  1356.                 }
  1357.             }
  1358.             
  1359.             if (id)
  1360.             {
  1361.                 elem = createValueElement(value, 'tag', tabindex);
  1362.             }
  1363.             else
  1364.             {
  1365.                 // Just remove the row
  1366.                 //
  1367.                 // If there's an open popup, this throws NODE CANNOT BE FOUND
  1368.                 try {
  1369.                     var row = rows.removeChild(row);
  1370.                 }
  1371.                 catch (e) {}
  1372.                 tagsbox.fixPopup();
  1373.                 
  1374.                 _tabDirection = false;
  1375.                 return;
  1376.             }
  1377.             
  1378.             var focusMode = 'tags';
  1379.             var focusBox = tagsbox;
  1380.         }
  1381.         
  1382.         // Fields
  1383.         else
  1384.         {
  1385.             // Access date needs to be parsed and converted to UTC
  1386.             if (fieldName=='accessDate' && value!='')
  1387.             {
  1388.                 if (Zotero.Date.isSQLDate(value) || Zotero.Date.isSQLDateTime(value)) {
  1389.                     var localDate = Zotero.Date.sqlToDate(value);
  1390.                     value = Zotero.Date.dateToSQL(localDate, true);
  1391.                 }
  1392.                 else {
  1393.                     var d = Zotero.Date.strToDate(value);
  1394.                     value = null;
  1395.                     if (d.year && d.month != undefined && d.day) {
  1396.                         d = new Date(d.year, d.month, d.day);
  1397.                         value = Zotero.Date.dateToSQL(d, true);
  1398.                     }
  1399.                 }
  1400.             }
  1401.             
  1402.             if (saveChanges) {
  1403.                 _modifyField(fieldName,value);
  1404.             }
  1405.             
  1406.             elem = createValueElement(_itemBeingEdited.getField(fieldName), fieldName, tabindex);
  1407.         }
  1408.         
  1409.         var box = textbox.parentNode;
  1410.         box.replaceChild(elem,textbox);
  1411.         
  1412.         if (_tabDirection)
  1413.         {
  1414.             if (!focusMode)
  1415.             {
  1416.                 var focusMode = 'info';
  1417.                 var focusBox = _dynamicFields;
  1418.             }
  1419.             _focusNextField(focusMode, focusBox, _lastTabIndex, _tabDirection==-1);
  1420.         }
  1421.     }
  1422.     
  1423.     function _modifyField(field, value)
  1424.     {
  1425.         _itemBeingEdited.setField(field,value);
  1426.         return _itemBeingEdited.save();
  1427.     }
  1428.     
  1429.     
  1430.     function _getFieldValue(field)
  1431.     {
  1432.         return field.firstChild
  1433.             ? field.firstChild.nodeValue : field.value;
  1434.     }
  1435.     
  1436.     function _setFieldValue(field, value)
  1437.     {
  1438.         if (field.firstChild)
  1439.         {
  1440.             field.firstChild.nodeValue = value;
  1441.         }
  1442.         else
  1443.         {
  1444.             field.value = value;
  1445.         }
  1446.     }
  1447.     
  1448.     
  1449.     // TODO: work with textboxes too
  1450.     function textTransform(label, mode) {
  1451.         var val = _getFieldValue(label);
  1452.         switch (mode) {
  1453.             case 'lower':
  1454.                 var newVal = val.toLowerCase();
  1455.                 break;
  1456.             case 'title':
  1457.                 var utils = new Zotero.Utilities();
  1458.                 var newVal = utils.capitalizeTitle(val.toLowerCase(), true);
  1459.                 break;
  1460.             default:
  1461.                 throw ("Invalid transform mode '" + mode + "' in ZoteroItemPane.textTransform()");
  1462.         }
  1463.         _setFieldValue(label, newVal);
  1464.         _modifyField(label.getAttribute('fieldname'), newVal);
  1465.     }
  1466.     
  1467.     
  1468.     function getCreatorFields(row){
  1469.         var typeID = row.getElementsByTagName('toolbarbutton')[0].getAttribute('typeid');
  1470.         var label1 = row.getElementsByTagName('hbox')[0].firstChild.firstChild;
  1471.         var label2 = label1.parentNode.lastChild;
  1472.         
  1473.         return {
  1474.             lastName: label1.firstChild ? label1.firstChild.nodeValue
  1475.                 : label1.value,
  1476.             firstName: label2.firstChild ? label2.firstChild.nodeValue
  1477.                 : label2.value,
  1478.             typeID: typeID,
  1479.             singleField: label1.getAttribute('singleField') == 'true'
  1480.         }
  1481.     }
  1482.     
  1483.     function modifyCreator(index, field, value, otherFields)
  1484.     {
  1485.         if (otherFields){
  1486.             var firstName = otherFields.firstName;
  1487.             var lastName = otherFields.lastName;
  1488.             var typeID = otherFields.typeID;
  1489.             var singleField = otherFields.singleField;
  1490.             
  1491.             // Ignore '(first)'/'(last)' or '(name)'
  1492.             if (singleField || firstName == _defaultFirstName){
  1493.                 firstName = '';
  1494.             }
  1495.             
  1496.             if (lastName==_defaultFullName || lastName == _defaultLastName){
  1497.                 lastName = '';
  1498.             }
  1499.         }
  1500.         else {
  1501.             var creator = _itemBeingEdited.getCreator(index);
  1502.             var firstName = creator['firstName'];
  1503.             var lastName = creator['lastName'];
  1504.             var typeID = creator['creatorTypeID'];
  1505.             var singleField = creator['singleField'];
  1506.         }
  1507.         
  1508.         // Don't save empty creators
  1509.         if (!_itemBeingEdited.hasCreatorAt(index) && !firstName && !lastName){
  1510.             return;
  1511.         }
  1512.         
  1513.         switch (field){
  1514.             case 'firstName':
  1515.                 firstName = value;
  1516.                 break;
  1517.             case 'lastName':
  1518.                 lastName = value;
  1519.                 break;
  1520.             case 'typeID':
  1521.                 typeID = value;
  1522.                 break;
  1523.             case 'singleField':
  1524.                 singleField = value;
  1525.                 break;
  1526.         }
  1527.         
  1528.         _itemBeingEdited.setCreator(index, firstName, lastName, typeID, singleField);
  1529.         _itemBeingEdited.save();
  1530.     }
  1531.     
  1532.     
  1533.     function removeNote(id)
  1534.     {
  1535.         var note = Zotero.Items.get(id);
  1536.         if(note)
  1537.             if(confirm(Zotero.getString('pane.item.notes.delete.confirm')))
  1538.                 note.erase();
  1539.     }
  1540.     
  1541.     function addNote()
  1542.     {
  1543.         ZoteroPane.openNoteWindow(null, null, _itemBeingEdited.getID());
  1544.     }
  1545.     
  1546.     function _noteToTitle(text)
  1547.     {
  1548.         var MAX_LENGTH = 100;
  1549.         
  1550.         var t = text.substring(0, MAX_LENGTH);
  1551.         var ln = t.indexOf("\n");
  1552.         if (ln>-1 && ln<MAX_LENGTH)
  1553.         {
  1554.             t = t.substring(0, ln);
  1555.         }
  1556.         
  1557.         if(t == "")
  1558.         {
  1559.             return Zotero.getString('pane.item.notes.untitled');
  1560.         }
  1561.         else
  1562.         {
  1563.             return t;
  1564.         }
  1565.     }
  1566.     
  1567.     function _updateNoteCount()
  1568.     {
  1569.         var c = _notesList.childNodes.length;
  1570.         
  1571.         var str = 'pane.item.notes.count.';
  1572.         switch (c){
  1573.             case 0:
  1574.                 str += 'zero';
  1575.                 break;
  1576.             case 1:
  1577.                 str += 'singular';
  1578.                 break;
  1579.             default:
  1580.                 str += 'plural';
  1581.                 break;
  1582.         }
  1583.         
  1584.         _notesLabel.value = Zotero.getString(str, [c]);
  1585.     }
  1586.     
  1587.     function _updateAttachmentCount()
  1588.     {
  1589.         var c = _attachmentsList.childNodes.length;
  1590.         
  1591.         var str = 'pane.item.attachments.count.';
  1592.         switch (c){
  1593.             case 0:
  1594.                 str += 'zero';
  1595.                 break;
  1596.             case 1:
  1597.                 str += 'singular';
  1598.                 break;
  1599.             default:
  1600.                 str += 'plural';
  1601.                 break;
  1602.         }
  1603.         
  1604.         _attachmentsLabel.value = Zotero.getString(str, [c]);
  1605.     }
  1606.     
  1607.     function removeAttachment(id)
  1608.     {
  1609.         var attachment = Zotero.Items.get(id);
  1610.         if(attachment)
  1611.             if(confirm(Zotero.getString('pane.item.attachments.delete.confirm')))
  1612.                 attachment.erase();
  1613.     }
  1614.     
  1615.     function addAttachmentFromDialog(link)
  1616.     {
  1617.         ZoteroPane.addAttachmentFromDialog(link, _itemBeingEdited.getID());
  1618.     }
  1619.     
  1620.     function addAttachmentFromPage(link)
  1621.     {
  1622.         ZoteroPane.addAttachmentFromPage(link, _itemBeingEdited.getID());
  1623.     }
  1624.     
  1625.     
  1626.     function focusFirstField(mode) {
  1627.         switch (mode) {
  1628.             case 'info':
  1629.                 _focusNextField('info', _dynamicFields, 0, false);
  1630.                 break;
  1631.         }
  1632.     }
  1633.     
  1634.     
  1635.     /*
  1636.      * Advance the field focus forward or backward
  1637.      *
  1638.      * Note: We're basically replicating the built-in tabindex functionality,
  1639.      * which doesn't work well with the weird label/textbox stuff we're doing.
  1640.      * (The textbox being tabbed away from is deleted before the blur()
  1641.      * completes, so it doesn't know where it's supposed to go next.)
  1642.      */
  1643.     function _focusNextField(mode, box, tabindex, back){
  1644.         tabindex = parseInt(tabindex);
  1645.         if (back)
  1646.         {
  1647.             if (mode=='info')
  1648.             {
  1649.                 switch (tabindex)
  1650.                 {
  1651.                     case 1:
  1652.                         //Zotero.debug('At beginning');
  1653.                         document.getElementById('zotero-editpane-type-menu').focus();
  1654.                         return false;
  1655.                     
  1656.                     case _tabIndexMinCreators:
  1657.                         var nextIndex = 1;
  1658.                         break;
  1659.                     
  1660.                     case _tabIndexMinFields:
  1661.                         var nextIndex = _tabIndexMaxCreators;
  1662.                         break;
  1663.                     
  1664.                     default:
  1665.                         var nextIndex = tabindex - 1;
  1666.                 }
  1667.             }
  1668.             else if (mode=='tags')
  1669.             {
  1670.                 switch (tabindex)
  1671.                 {
  1672.                     case 1:
  1673.                         return false;
  1674.                     
  1675.                     default:
  1676.                         var nextIndex = tabindex - 1;
  1677.                 }
  1678.             }
  1679.         }
  1680.         else
  1681.         {
  1682.             if (mode=='info')
  1683.             {
  1684.                 switch (tabindex)
  1685.                 {
  1686.                     case 1:
  1687.                         var nextIndex = _tabIndexMinCreators;
  1688.                         break;
  1689.                     
  1690.                     case _tabIndexMaxCreators:
  1691.                         var nextIndex = _tabIndexMinFields;
  1692.                         break;
  1693.                     
  1694.                     case _tabIndexMaxInfoFields:
  1695.                         //Zotero.debug('At end');
  1696.                         return false;
  1697.                     
  1698.                     default:
  1699.                         var nextIndex = tabindex + 1;
  1700.                 }
  1701.             }
  1702.             else if (mode=='tags')
  1703.             {
  1704.                 switch (tabindex)
  1705.                 {
  1706.                     case _tabIndexMaxTagsFields:
  1707.                         // In tags box, keep going to create new row
  1708.                         var nextIndex = tabindex + 1;
  1709.                         break;
  1710.                     
  1711.                     default:
  1712.                         var nextIndex = tabindex + 1;
  1713.                 }
  1714.             }
  1715.         }
  1716.         
  1717.         Zotero.debug('Looking for tabindex ' + nextIndex, 4);
  1718.         switch (mode)
  1719.         {
  1720.             case 'info':
  1721.                 var next = box.getElementsByAttribute('ztabindex', nextIndex);
  1722.                 if (!next[0])
  1723.                 {
  1724.                     //Zotero.debug("Next field not found");
  1725.                     return _focusNextField(mode, box, nextIndex, back);
  1726.                 }
  1727.                 break;
  1728.             
  1729.             // Tags pane
  1730.             case 'tags':
  1731.                 var next = document.getAnonymousNodes(box)[0].
  1732.                     getElementsByAttribute('ztabindex', nextIndex);
  1733.                 if (!next[0]){
  1734.                     next[0] = box.addDynamicRow();
  1735.                 }
  1736.                 break;
  1737.         }
  1738.         
  1739.         next[0].click();
  1740.         ensureElementIsVisible(next[0]);
  1741.         return true;
  1742.     }
  1743. }
  1744.  
  1745. addEventListener("load", function(e) { ZoteroItemPane.onLoad(e); }, false);
  1746.